package com.binance.assetservice.web.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.binance.account.api.UserApi;
import com.binance.account.vo.security.request.UserIdRequest;
import com.binance.account.vo.user.UserInfoVo;
import com.binance.account.vo.user.request.GetUserRequest;
import com.binance.account.vo.user.response.GetUserResponse;
import com.binance.assetservice.data.mapper.UserAssetDribbletItemMapper;
import com.binance.assetservice.data.mapper.UserAssetItemMapper;
import com.binance.assetservice.data.models.ProductItem;
import com.binance.assetservice.data.models.UserAssetDribbletItem;
import com.binance.assetservice.data.models.UserAssetItem;
import com.binance.assetservice.vo.request.AssetTransferRequest;
import com.binance.assetservice.vo.request.AsssetBalanceRequest;
import com.binance.assetservice.vo.request.DeductionFeeRequest;
import com.binance.assetservice.vo.request.DribbletAssetToBNBRequest;
import com.binance.assetservice.vo.request.FreezeAssetRequest;
import com.binance.assetservice.vo.request.GetOneUserAssetRequest;
import com.binance.assetservice.vo.request.GetTranRequest;
import com.binance.assetservice.vo.request.GetUserAssetRequest;
import com.binance.assetservice.vo.request.SyncBalanceRequest;
import com.binance.assetservice.vo.request.UserAssetTransferBtcRequest;
import com.binance.assetservice.vo.request.UserAssetTransferDribbletBtcRequest;
import com.binance.assetservice.vo.request.UserNegativeAssetRequest;
import com.binance.assetservice.vo.request.WithdrawAssetFailRequest;
import com.binance.assetservice.vo.request.WithdrawAssetRequest;
import com.binance.assetservice.vo.request.WithdrawAssetSuccessRequest;
import com.binance.assetservice.vo.response.DribbletAssetToBNBResponse;
import com.binance.assetservice.vo.response.DribbletAssetToBNBResponse.TransferResult;
import com.binance.assetservice.vo.response.PagingResult;
import com.binance.assetservice.vo.response.UserAssetTransferBtcResponse;
import com.binance.assetservice.vo.response.UserAssetTransferBtcResponse.AssetTransferBtc;
import com.binance.assetservice.vo.response.UserAssetTransferDribbletBtcResponse;
import com.binance.assetservice.vo.response.UserAssetTransferDribbletBtcResponse.UserDribbletAsset;
import com.binance.assetservice.web.service.IProductService;
import com.binance.assetservice.web.service.ITranService;
import com.binance.assetservice.web.service.IUserAssetLogService;
import com.binance.assetservice.web.service.IUserAssetService;
import com.binance.assetservice.web.utils.Constants;
import com.binance.distcache.distlock.DistLockManager;
import com.binance.distcache.distlock.IDistLock;
import com.binance.master.error.BusinessException;
import com.binance.master.error.GeneralCode;
import com.binance.master.utils.CopyBeanUtils;
import com.binance.master.utils.FormatUtils;
import com.binance.master.utils.RedisCacheUtils;
import com.binance.mbxgateway.api.IMatchboxApi;
import com.binance.mbxgateway.vo.request.matchbox.GetAccountRequest;
import com.binance.mbxgateway.vo.request.matchbox.PostBalanceRequest;
import com.binance.sysconf.service.SysConfigVarCacheService;
import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.RetryListener;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.javasimon.aop.Monitored;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import static java.math.BigDecimal.ZERO;

@Monitored
@Service
@Slf4j
public class UserAssetServiceImpl extends BaseServiceImpl implements IUserAssetService, SmartInitializingSingleton {

    @Value("${matchbox.management.root.url}")
    private String mgmtUrl;

    @Resource
    private RestTemplate template;

    @Autowired
    private IUserAssetLogService userAssetLogService;
    @Autowired
    private UserAssetItemMapper userAssetMapper;
    @Autowired
    private UserAssetDribbletItemMapper userAssetDribbletItemMapper;
    // @Autowired
    // private TradeItemMapper tradeItemMapper;
    // @Autowired
    // private TradingLedgerItemMapper tradingLedgerItemMapper;
    @Autowired
    private ITranService tranService;
    @Autowired
    private IProductService productService;

    @Autowired
    private SysConfigVarCacheService sysConfigService;
    @Autowired
    private DistLockManager distLockManager;
    @Resource
    private UserApi userApi;
    @Resource
    private IMatchboxApi matchboxApi;

    private static final int MAX_RETRY = 20;
    private static final int SLEEP_TIME = 1;
    private static final String QUOTA_ASSET = "BTC";
    private static final String BNB_ASSET = "BNB";
    private static String EXCHANGE_DRIBBLET_PERCENTAGE = "";
    private static BigDecimal TRANSFER_BASE = null;
    private static BigDecimal TRANSFER_UP_FLOAT = null;
    private static Integer DRIBBLET_REQUEST_EXPIRES = 0; // 24h
    private static String DRIBBLET_REQUEST_REDIS_PREFIX = "dribblet_request";
    private static Integer ENABLE_REDIS_EXPIRES = 0; // 24h
    private static final int ASSET_PRECISION = 8;
    private RetryListener listener = new RetryListener() {

        @Override
        public <V> void onRetry(Attempt<V> attempt) {
            if (attempt.hasException()) {
                log.warn(String.format("%d retry after %d ms", attempt.getAttemptNumber(),
                        attempt.getDelaySinceFirstAttempt()), attempt.getExceptionCause());
                if (attempt.getExceptionCause().getCause().getMessage().contains(
                        "The MySQL server is running with the --read-only option so it cannot execute this statement")) {
                    log.error("Failed to point to new master during the failover, exiting jvm",
                            attempt.getExceptionCause());
                    System.exit(-1);
                }
            }
        }

    };
    private final Retryer<Integer> retryer =
            RetryerBuilder.<Integer>newBuilder().retryIfExceptionOfType(Throwable.class).withRetryListener(listener)
                    .withWaitStrategy(WaitStrategies.fixedWait(SLEEP_TIME, TimeUnit.SECONDS))
                    .withStopStrategy(StopStrategies.stopAfterAttempt(MAX_RETRY)).build();

    private final Retryer<UserAssetItem> userAssetRetryer = RetryerBuilder.<UserAssetItem>newBuilder()
            .retryIfExceptionOfType(Throwable.class).withRetryListener(listener)
            .withWaitStrategy(WaitStrategies.fixedWait(SLEEP_TIME, TimeUnit.SECONDS))
            .withStopStrategy(StopStrategies.stopAfterAttempt(MAX_RETRY)).build();

    // private LoadingCache<String, String> userDribbletAssetCache = null;
    /** 平台收取手续费账户 */
    private static String platformUserID;
    private static UserInfoVo exchanegAgent;
    private static String superAgentId;
    private static String defaultAgentId;
    private static BigDecimal exchangeAgentRatio;

    @PostConstruct
    public void init() {
        // userAssetLock =
        // CacheBuilder.newBuilder().redisAddresses(redisAddresses).bufferSize(2048).redisPassword(redisPassword)
        // .expireAfterAccess(12,
        // TimeUnit.HOURS).prefix(CacheNamespace.CACHE_USER_ASSET_LOCK).build();
        // userDribbletAssetCache =
        // CacheBuilder.newBuilder().redisAddresses(redisAddresses).bufferSize(2048)
        // .redisPassword(redisPassword).build();

        superAgentId = this.sysConfigService.getValue(Constants.EXCHANGE_AGNET);
        defaultAgentId = this.sysConfigService.getValue(Constants.DEFAULT_AGNET);
        exchangeAgentRatio = new BigDecimal(this.sysConfigService.getValue(Constants.EXCHANGE_AGNET_RATIO));
        UserIdRequest request = new UserIdRequest();
        request.setUserId(Long.parseLong(superAgentId));
        try {
            GetUserResponse response =
                    this.getAPIRequestResponse(this.userApi.getUserById(this.newAPIRequest(request)));
            exchanegAgent = response.getUserInfo();
        } catch (BusinessException e) {
            log.error("获取经纪人出现异常", e);
//            throw e;
        } catch (Exception e) {
            log.error("获取经纪人出现异常", e);
//            throw new RuntimeException(e);
        }
    }

    @Override
    public PagingResult<UserAssetItem> getUserAsset(GetUserAssetRequest request) throws Exception {
        // GetUserResponse userResponse = this.getUser(request.getUserId());
        // UserInfoVo user = userResponse.getUserInfo();
        // // To Do 此处是否要判断用户状态？
        Integer start = null;
        Integer rows = null;
        Integer pageIndex = request.getPageIndex();
        if (pageIndex != null) {
            if (pageIndex <= 0) {
                pageIndex = 1;
            }
        }
        Integer pageSize = request.getPageSize();
        if (pageSize != null) {
            if (pageSize <= 0) {
                pageSize = 20;
            }
        }
        if (pageIndex != null && pageSize != null) {
            start = (pageIndex - 1) * pageSize;
            rows = pageSize;
        }
        List<UserAssetItem> list =
                this.userAssetMapper.getUserAsset(start, rows, request.getUserIdList(), request.getAsset());
        long total = this.userAssetMapper.getUserAssetCount(request.getUserIdList(), request.getAsset());
        return new PagingResult<UserAssetItem>(list, total);
    }

    @Override
    public void withdrawAsset(WithdrawAssetRequest request) throws Exception {
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException(GeneralCode.AMOUNT_OVER_ZERO);
        }
        GetUserResponse userResponse = this.getCheckedUser(request.getUserId());
        UserInfoVo user = userResponse.getUserInfo();
        StringBuilder sb = new StringBuilder();
        sb.append(request.getUserId()).append(":");
        sb.append(request.getAsset());
        String key = sb.toString();
        IDistLock lock = this.distLockManager.buildRedDistLock(key);
        // Long lockId = null;
        try {
            // lockId = this.userAssetLock.lock(key);
            if (!lock.tryLock(20, -1, TimeUnit.SECONDS)) {
                throw new BusinessException(GeneralCode.GET_REDIS_LOCKED_FAILED);
            }
            // 查询用户资产
            UserAssetItem userAsset =
                    this.userAssetMapper.getUserAssetByUidAndAsset(request.getUserId(), request.getAsset());
            // 验证资产
            if (userAsset == null) {
                throw new BusinessException(GeneralCode.ASSET_USER_HAVE_NO_ASSET);
            }
            if (userAsset.getFree().compareTo(request.getAmount()) < 0) {
                throw new BusinessException(GeneralCode.ASSET_ASSET_NOT_ENOUGH);
            }
            // 更新资产
            userAsset.setFree(userAsset.getFree().subtract(request.getAmount()));
            userAsset.setWithdrawing(userAsset.getWithdrawing().add(request.getAmount()));
            String desc = StringUtils.defaultIfBlank(request.getInfo(), "资产提现");

            PostBalanceRequest postBalanceRequest = new PostBalanceRequest();
            postBalanceRequest.setAccountId(String.valueOf(user.getTradingAccount()));
            postBalanceRequest.setAsset(request.getAsset());
            postBalanceRequest.setUpdateId(String.valueOf(request.getTranId()));
            postBalanceRequest
                    .setBalanceDelta(FormatUtils.getAssetNumericFormatter().format(ZERO.subtract(request.getAmount())));
            this.getAPIRequestResponse(this.matchboxApi.postBalance(this.newAPIRequest(postBalanceRequest)));

            // UriComponentsBuilder builder =
            // UriComponentsBuilder.fromHttpUrl(String.format("%s/balance", mgmtUrl))
            // .queryParam("accountId", user.getTradingAccount()).queryParam("asset",
            // request.getAsset())
            // .queryParam("updateId", request.getTranId()).queryParam("balanceDelta",
            // FormatUtils.getAssetNumericFormatter().format(ZERO.subtract(request.getAmount())));
            // this.template.postForObject(builder.toUriString(), null, String.class);
            userAsset.setModifiedAt(new Date());
            retryer.call(() -> this.userAssetMapper.updateUserAsset(userAsset));
            this.userAssetLogService.addAssetLog(request.getTranId(), request.getType(), request.getAsset(),
                    request.getUserId(), ZERO, desc, userAsset);
        } finally {
            lock.unlock();
            // if (lockId != null) {
            // this.userAssetLock.unlock(key, lockId);
            // }
        }
    }

    @Override
    public void withdrawAssetSuccess(WithdrawAssetSuccessRequest request) throws Exception {
        if (request.getAmount().compareTo(ZERO) <= 0) {
            throw new BusinessException(GeneralCode.AMOUNT_OVER_ZERO);
        }
        this.getCheckedUser(request.getUserId());
        StringBuilder sb = new StringBuilder();
        sb.append(request.getUserId()).append(":");
        sb.append(request.getAsset());
        String key = sb.toString();
        IDistLock lock = this.distLockManager.buildRedDistLock(key);
        log.info("get lock before asset withdrawSuccess ");
        try {
            // lockId = userAssetLock.lock(key);
            if (!lock.tryLock(20, -1, TimeUnit.SECONDS)) {
                throw new BusinessException(GeneralCode.GET_REDIS_LOCKED_FAILED);
            }
            UserAssetItem userAsset =
                    this.userAssetMapper.getUserAssetByUidAndAsset(request.getUserId(), request.getAsset());
            if (userAsset == null) {
                throw new BusinessException(GeneralCode.ASSET_USER_HAVE_NO_ASSET);
            }
            if (userAsset.getWithdrawing().compareTo(request.getAmount()) < 0) {
                throw new BusinessException(GeneralCode.ASSET_ASSET_NOT_ENOUGH);
            }
            // 更新提现数量
            userAsset.setWithdrawing(userAsset.getWithdrawing().subtract(request.getAmount()));
            String desc = StringUtils.defaultIfBlank(request.getInfo(), "资产提现成功");
            userAsset.setModifiedAt(new Date());
            this.userAssetMapper.updateUserAsset(userAsset);
            this.userAssetLogService.addAssetLog(request.getTranId(), request.getType(), request.getAsset(),
                    request.getUserId(), ZERO.subtract(request.getAmount()), desc, userAsset);
        } finally {
            lock.unlock();
            // if (lockId != null) {
            // this.userAssetLock.unlock(key, lockId);
            // }
        }
    }

    @Override
    public void withdrawassetFail(WithdrawAssetFailRequest request) throws Exception {
        if (request.getAmount().compareTo(ZERO) <= 0) {
            throw new BusinessException(GeneralCode.AMOUNT_OVER_ZERO);
        }
        GetUserResponse userResponse = this.getCheckedUser(request.getUserId());
        UserInfoVo user = userResponse.getUserInfo();
        // Long lockId = null;
        StringBuilder sb = new StringBuilder();
        sb.append(request.getUserId()).append(":");
        sb.append(request.getAsset());
        String key = sb.toString();
        IDistLock lock = this.distLockManager.buildRedDistLock(key);
        log.info("get lock before asset withdrawassetFail ");
        try {
            if (!lock.tryLock(20, -1, TimeUnit.SECONDS)) {
                throw new BusinessException(GeneralCode.GET_REDIS_LOCKED_FAILED);
            }
            UserAssetItem userAsset =
                    this.userAssetMapper.getUserAssetByUidAndAsset(request.getUserId(), request.getAsset());
            log.info("invoke  this.userAssetMapper.getUserAssetByUidAndAsset complete");
            if (userAsset == null) {
                throw new BusinessException(GeneralCode.ASSET_USER_HAVE_NO_ASSET);
            }
            if (userAsset.getWithdrawing().compareTo(request.getAmount()) < 0) {
                throw new BusinessException(GeneralCode.ASSET_WITHDRAW_WITHDRAWING_NOT_ENOUGH);
            }
            log.info("userAsset基本校验通过......");
            // 更新资产
            userAsset.setFree(userAsset.getFree().add(request.getAmount()));
            userAsset.setWithdrawing(userAsset.getWithdrawing().subtract(request.getAmount()));
            String desc = StringUtils.defaultIfBlank(request.getInfo(), "资产提现失败，退回资产");

            // UriComponentsBuilder builder =
            // UriComponentsBuilder.fromHttpUrl(String.format("%s/balance", mgmtUrl))
            // .queryParam("accountId", user.getTradingAccount()).queryParam("asset",
            // userAsset.getAsset())
            // .queryParam("updateId", request.getTranId())
            // .queryParam("balanceDelta",
            // FormatUtils.getAssetNumericFormatter().format(request.getAmount()));
            // String url = builder.toUriString();
            // log.debug("Sending request: {} to match box", url);
            // 先扣钱再往撮合加钱
            Map<String, Object> param = new HashMap<String, Object>();
            param.put("uid", String.valueOf(user.getUserId()));
            param.put("asset", userAsset.getAsset());
            param.put("amount", request.getAmount());
            log.info("invoke this.userAssetMapper.updateWithdrawingFail(param)");
            int resultCount = this.userAssetMapper.updateWithdrawingFail(param);
            log.info("invoke this.userAssetMapper.updateWithdrawingFail(param) complete");
            if (resultCount > 0) {
                PostBalanceRequest postBalanceRequest = new PostBalanceRequest();
                postBalanceRequest.setAccountId(String.valueOf(user.getTradingAccount()));
                postBalanceRequest.setAsset(userAsset.getAsset());
                postBalanceRequest.setUpdateId(String.valueOf(request.getTranId()));
                postBalanceRequest.setBalanceDelta(FormatUtils.getAssetNumericFormatter().format(request.getAmount()));
                this.getAPIRequestResponse(this.matchboxApi.postBalance(this.newAPIRequest(postBalanceRequest)));
                // template.postForObject(url, null, String.class);
                this.userAssetLogService.addAssetLog(request.getTranId(), request.getType(), request.getAsset(),
                        request.getUserId(), ZERO, desc, userAsset);
            } else {
                throw new BusinessException(GeneralCode.SYS_ERROR);
            }
        } catch (Exception e) {
            log.error("withdrawassetFail execute fail:", e);
            throw e;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void assetBalance(AsssetBalanceRequest request) throws Exception {
        if (request.getAmount().compareTo(ZERO) == 0) {
            return;
        }
        try {
            String strAmount = FormatUtils.getAssetNumericFormatter().format(request.getAmount());
            request.setAmount(new BigDecimal(strAmount));
            GetUserResponse userResponse = this.getCheckedUser(request.getUserId());
            UserInfoVo user = userResponse.getUserInfo();
            UserAssetItem userAsset =
                    this.userAssetMapper.getUserAssetByUidAndAsset(request.getUserId(), request.getAsset());
            if (userAsset == null) {
                userAsset = new UserAssetItem();
                userAsset.setUid(request.getUserId());
                userAsset.setAsset(request.getAsset());
                userAsset.setFree(request.getAmount());
                userAsset.setLocked(ZERO);
                userAsset.setFreeze(ZERO);
                userAsset.setIpoing(ZERO);
                userAsset.setIpoable(ZERO);
                userAsset.setStorage(ZERO);
                Date now = new Date();
                userAsset.setCreatedAt(now);
                userAsset.setModifiedAt(now);
            } else {
                userAsset.setFree(userAsset.getFree().add(request.getAmount()));
            }

            PostBalanceRequest postBalanceRequest = new PostBalanceRequest();
            postBalanceRequest.setAccountId(String.valueOf(user.getTradingAccount()));
            postBalanceRequest.setAsset(request.getAsset());
            postBalanceRequest.setUpdateId(String.valueOf(request.getTranId()));
            postBalanceRequest.setBalanceDelta(strAmount);
            this.getAPIRequestResponse(this.matchboxApi.postBalance(this.newAPIRequest(postBalanceRequest)));

            // UriComponentsBuilder builder =
            // UriComponentsBuilder.fromHttpUrl(String.format("%s/balance", mgmtUrl))
            // .queryParam("accountId", user.getTradingAccount()).queryParam("asset",
            // request.getAsset())
            // .queryParam("updateId", request.getTranId()).queryParam("balanceDelta", strAmount);
            // String url = builder.build().toUriString();
            // log.debug("Sending request: {} to match box", url);
            // template.postForObject(url, null, String.class);
            this.userAssetLogService.addAssetLog(request.getTranId(), request.getType(), request.getAsset(),
                    request.getUserId(), request.getAmount(),
                    StringUtils.isNotBlank(request.getInfo()) ? request.getInfo()
                            : (request.getAmount().compareTo(ZERO) > 0 ? "存入资产" : "提取资产"),
                    userAsset);
        } catch (HttpStatusCodeException e) {
            String responseString = e.getResponseBodyAsString();
            log.error("assetBalance error is :{}", responseString, e);
            throw e;
        } catch (Exception e) {
            log.error("assetBalance error is :", e);
            throw e;
        }
    }

    @Override
    public void freezeAsset(FreezeAssetRequest request) throws Exception {
        if (request.getAmount().compareTo(ZERO) < 0) {
            throw new BusinessException(GeneralCode.AMOUNT_OVER_ZERO);
        }
        GetUserResponse userResponse = this.getCheckedUser(request.getUserId());
        UserInfoVo user = userResponse.getUserInfo();
        StringBuilder sb = new StringBuilder();
        sb.append(user.getUserId()).append(":");
        sb.append(request.getAsset());
        String key = sb.toString();
        IDistLock lock = this.distLockManager.buildRedDistLock(key);
        log.info("get lock before asset freezeAsset ");
        try {
            if (!lock.tryLock(20, -1, TimeUnit.SECONDS)) {
                throw new BusinessException(GeneralCode.GET_REDIS_LOCKED_FAILED);
            }
            UserAssetItem userAsset =
                    this.userAssetMapper.getUserAssetByUidAndAsset(request.getUserId(), request.getAsset());
            BigDecimal delta = request.getAmount().subtract(userAsset.getFreeze());
            userAsset.setFree(ZERO.subtract(delta));
            userAsset.setFreeze(delta);

            PostBalanceRequest postBalanceRequest = new PostBalanceRequest();
            postBalanceRequest.setAccountId(String.valueOf(user.getTradingAccount()));
            postBalanceRequest.setAsset(request.getAsset());
            postBalanceRequest.setUpdateId(String.valueOf(request.getTranId()));
            postBalanceRequest.setBalanceDelta(FormatUtils.getAssetNumericFormatter().format(ZERO.subtract(delta)));
            this.getAPIRequestResponse(this.matchboxApi.postBalance(this.newAPIRequest(postBalanceRequest)));

            // UriComponentsBuilder builder =
            // UriComponentsBuilder.fromHttpUrl(String.format("%s/balance", mgmtUrl))
            // .queryParam("accountId", user.getTradingAccount()).queryParam("asset",
            // request.getAsset())
            // .queryParam("updateId", request.getTranId())
            // .queryParam("balanceDelta",
            // FormatUtils.getAssetNumericFormatter().format(ZERO.subtract(delta)));
            // String url = builder.build().toUriString();
            // log.debug("Sending request: {} to match box", url);
            // template.postForObject(url, null, String.class);
            this.userAssetMapper.updateUserAssetFreeze(userAsset);
            this.userAssetLogService.addAssetLog(request.getTranId(), request.getType(), request.getAsset(),
                    request.getUserId(), ZERO, delta.compareTo(ZERO) > 0 ? "冻结资产" : "解冻资产", userAsset);
        } catch (Exception e) {
            log.error("freezeAsset error is ", e);
            throw e;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void syncBalance(SyncBalanceRequest request) throws Exception {
        GetUserResponse userResponse = this.getCheckedUser(request.getUserId());
        UserInfoVo user = userResponse.getUserInfo();
        GetAccountRequest getAccountRequest = new GetAccountRequest();
        getAccountRequest.setAccountId(String.valueOf(user.getTradingAccount()));
        String jsonResult =
                this.getAPIRequestResponse(this.matchboxApi.getAccount(this.newAPIRequest(getAccountRequest)));

        // UriComponentsBuilder builder =
        // UriComponentsBuilder.fromHttpUrl(String.format("%s/account", mgmtUrl))
        // .queryParam("accountId", user.getTradingAccount());
        // String jsonResult = template.getForObject(builder.build().toUri(), String.class);
        JSONObject obj = JSON.parseObject(jsonResult);
        JSONArray jsonArray = obj.getJSONArray("balances");
        List<UserAssetItem> userAssetList =
                this.userAssetMapper.getUserAsset(null, null, Arrays.asList(request.getUserId()), null);
        Map<String, UserAssetItem> map = new HashMap<String, UserAssetItem>();
        for (int i = 0; i < userAssetList.size(); i++) {
            UserAssetItem userAssetItem = userAssetList.get(i);
            map.put(userAssetItem.getAsset(), userAssetItem);
        }
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject json = (JSONObject) jsonArray.get(i);
            UserAssetItem userAssetItem = map.get(json.get("asset"));
            if (userAssetItem != null) {
                if (userAssetItem.getFree().compareTo(new BigDecimal(String.valueOf(json.getDouble("free")))) != 0
                        || userAssetItem.getLocked()
                                .compareTo(new BigDecimal(String.valueOf(json.getDouble("locked")))) != 0) {
                    userAssetItem.setFree(new BigDecimal(String.valueOf(json.getDouble("free"))));
                    userAssetItem.setLocked(new BigDecimal(String.valueOf(json.getDouble("locked"))));
                    this.userAssetMapper.insertOrUpdateUserAsset(userAssetItem);
                }
            } else {
                if (!(json.getDouble("free") == 0 && json.getDouble("locked") == 0)) {
                    userAssetItem = new UserAssetItem();
                    userAssetItem.setUid(request.getUserId());
                    userAssetItem.setAsset(json.get("asset").toString());
                    userAssetItem.setFree(new BigDecimal(String.valueOf(json.getDouble("free"))));
                    userAssetItem.setLocked(new BigDecimal(String.valueOf(json.getDouble("locked"))));
                    this.userAssetMapper.insertOrUpdateUserAsset(userAssetItem);
                }
            }
            map.remove(json.get("asset"));
        }
        if (MapUtils.isNotEmpty(map)) {
            for (Map.Entry<String, UserAssetItem> entry : map.entrySet()) {
                System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
                if (!jsonResult.contains(entry.getKey())) {
                    UserAssetItem userAssetItem = map.get(entry.getKey());
                    if (userAssetItem != null) {
                        userAssetItem.setFree(BigDecimal.ZERO);
                        userAssetItem.setLocked(BigDecimal.ZERO);
                        if(StringUtils.isNotBlank(userAssetItem.getUid())){
                            this.userAssetMapper.insertOrUpdateUserAsset(userAssetItem);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void deductionFee(DeductionFeeRequest request) throws Exception {
        // 查询付款的币种类型
        String infoFeeAsset = this.sysConfigService.getValue("vote_fee_asset");
        // 查询收款人ID
        String infoFeeReceiveUser = this.sysConfigService.getValue("exch_agent");

        // 获取请求参数
        final String email = request.getEmail();
        final String fee = request.getFee();
        final String description = request.getDescription();
        final String assertType = request.getAssertType();

        // 验证费用必须大于0
        BigDecimal payFee = new BigDecimal(fee);
        if (payFee.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException(GeneralCode.SYS_VALID, "info.fee_error");
        }
        // 查询用户是否存在
        GetUserRequest body = new GetUserRequest();
        body.setEmail(email);
        GetUserResponse userResp = this.getAPIRequestResponse(this.userApi.getUserByEmail(this.newAPIRequest(body)));
        if (userResp == null || userResp.getUser() == null) {
            throw new BusinessException(GeneralCode.SYS_VALID, "info.email_not_found");
        }
        // 判断余额是否足够
        UserAssetItem userAssetItem = this.userAssetMapper
                .getUserAssetByUidAndAsset(String.valueOf(userResp.getUser().getUserId()), infoFeeAsset);
        if (userAssetItem == null || payFee.compareTo(userAssetItem.getFree()) > 0) {// 判断手续费是否大于剩余资产
            throw new BusinessException(GeneralCode.SYS_VALID, "info.asset_not_enough");
        }
        // 记录交易信息
        GetTranRequest item = new GetTranRequest();
        item.setTime(new Date());
        item.setTranType(22);
        item.setDescription(description);
        Long tranId = this.tranService.insert(item);
        // 扣除费用
        // 用户资产加上手续费的负值
        this.updateUserAsset(String.valueOf(userResp.getUser().getUserId()), payFee.negate(), infoFeeAsset,
                tranId.longValue(), assertType);
        // 收款人加上手续费
        this.updateUserAsset(infoFeeReceiveUser, payFee, infoFeeAsset, tranId, assertType);
    }

    /**
     * 更新用户资产
     *
     * @param userId
     * @param amount
     * @param asset
     * @param tranId
     * @return void
     * @throws Exception
     */
    private void updateUserAsset(String userId, BigDecimal amount, String asset, long tranId, String assertType)
            throws Exception {
        if (amount.compareTo(BigDecimal.ZERO) == 0) {
            return;
        }
        String strAmount = FormatUtils.getAssetNumericFormatter().format(amount);
        amount = new BigDecimal(strAmount);

        GetUserResponse userResp = this.getCheckedUser(userId);
        UserInfoVo user = userResp.getUserInfo();
        UserAssetItem userAsset = this.userAssetMapper.getUserAssetByUidAndAsset(userId, asset);
        if (null == userAsset) {
            userAsset = new UserAssetItem();
            userAsset.setUid(String.valueOf(user.getUserId()));
            userAsset.setAsset(asset);
            userAsset.setFree(amount);
            userAsset.setLocked(BigDecimal.ZERO);
            userAsset.setFreeze(BigDecimal.ZERO);
            userAsset.setIpoing(BigDecimal.ZERO);
            userAsset.setIpoable(BigDecimal.ZERO);
            userAsset.setStorage(BigDecimal.ZERO);
            Date now = new Date();
            userAsset.setCreatedAt(now);
            userAsset.setModifiedAt(now);
        } else {
            userAsset.setFree(userAsset.getFree().add(amount));
        }
        PostBalanceRequest postBalanceRequest = new PostBalanceRequest();
        postBalanceRequest.setAccountId(String.valueOf(user.getTradingAccount()));
        postBalanceRequest.setAsset(asset);
        postBalanceRequest.setUpdateId(String.valueOf(tranId));
        postBalanceRequest.setBalanceDelta(strAmount);
        this.getAPIRequestResponse(this.matchboxApi.postBalance(this.newAPIRequest(postBalanceRequest)));

        // UriComponentsBuilder builder =
        // UriComponentsBuilder.fromHttpUrl(String.format("%s/balance", mgmtUrl))
        // .queryParam("accountId", user.getTradingAccount()).queryParam("asset", asset)
        // .queryParam("updateId", tranId).queryParam("balanceDelta", strAmount);
        // String url = builder.build().toUriString();
        // log.debug("Sending request: {} to match box", url);
        // template.postForObject(url, null, String.class);
        int type = Constants.INFO_ASSET_TRANSFER;
        if (StringUtils.isNotBlank(assertType)) {
            type = Integer.parseInt(assertType);
        }
        this.userAssetLogService.addAssetLog(tranId, type, asset, userId, amount, "INFO手续费", userAsset);
    }

    @Override
    public UserAssetTransferBtcResponse userAssetTransferBtc(UserAssetTransferBtcRequest request) throws Exception {
        UserAssetTransferBtcResponse response = new UserAssetTransferBtcResponse();
        List<UserAssetItem> userAssetList =
                this.userAssetMapper.getUserAsset(null, null, Arrays.asList(request.getUserId()), null);
        BigDecimal totalTransferBtc = ZERO;
        String maxAsset = null;
        Map<String, BigDecimal> result = new HashMap<String, BigDecimal>();
        for (UserAssetItem userAsset : userAssetList) {
            log.info("userAssetTransferBtc the user's asset is {}",JSON.toJSONString(userAsset));
            BigDecimal price = BigDecimal.ZERO;
            try {
                price = getPrice(userAsset.getAsset(), QUOTA_ASSET, null);
                log.info("userAssetTransferBtc {} getPrice is {}",userAsset.getAsset(),price);
                if (price == null) {
                    price = BigDecimal.ZERO;
                }
            } catch (Exception e) {
                log.error("获取资产到BTC的汇率出现错误：",e);
                price = BigDecimal.ZERO;
            }
            BigDecimal totalAsset = new BigDecimal(String.valueOf(userAsset.getFree()))
                    .add(new BigDecimal(String.valueOf(userAsset.getFreeze())))
                    .add(new BigDecimal(String.valueOf(userAsset.getLocked())))
                    .add(new BigDecimal(String.valueOf(userAsset.getWithdrawing())));
            log.info("userAssetTransferBtc the userAssetTransferBtc is {}",totalAsset.toPlainString());
            BigDecimal transferBtc = ZERO;
            if (userAsset.getAsset().equals(QUOTA_ASSET)) {
                transferBtc = totalAsset;
            } else {
                transferBtc = price.multiply(totalAsset);
            }
            log.info("userAssetTransferBtc the transferBtc is {}",transferBtc.toPlainString());
            if (maxAsset == null) {
                maxAsset = userAsset.getAsset();
            } else if (result.get(maxAsset).compareTo(transferBtc) < 0) {
                maxAsset = userAsset.getAsset();
            }

            totalTransferBtc = totalTransferBtc.add(transferBtc);

            BigDecimal v = transferBtc.setScale(8, BigDecimal.ROUND_HALF_UP);
            log.info("userAssetTransferBtc the resp transferBtc is {}",v.toPlainString());
            result.put(userAsset.getAsset(), v);

            AssetTransferBtc assetTransferBtc = new AssetTransferBtc();
            assetTransferBtc.setAsset(userAsset.getAsset());
            assetTransferBtc.setTransferBtc(v);
            response.getAssetTransferBtcList().add(assetTransferBtc);
        }
        response.setTotalTransferBtc(totalTransferBtc);
        response.setMaxAsset(maxAsset);
        return response;
    }

    @Override
    public DribbletAssetToBNBResponse dribbletAssetToBNB(DribbletAssetToBNBRequest request) throws Exception {
        // 判断小额资产开关是否打开。如果未配置或打开，可以兑换。如果未打开，不执行兑换逻辑。
        String dribbletConvertSwitch = this.sysConfigService.getValue("dribblet_convert_switch");
        if ("0".equals(dribbletConvertSwitch) || "off".equals(dribbletConvertSwitch)
                || "false".equals(dribbletConvertSwitch)) {
            log.info("小额资产转换开关dribblet_convert_switch关闭，无法进行转换。");
            throw new BusinessException(GeneralCode.ASSET_DRIBBLET_CONVERT_SWITCH_OFF);
        }
        GetUserResponse userResponse = this.getCheckedUser(request.getUserId());
        List<String> assetList = request.getAssetList();
        // 获取用户所有资产
        List<UserAssetItem> userAssets =
                this.userAssetMapper.getUserAsset(null, null, Arrays.asList(request.getUserId()), null);

        // 暂存某个币种到BTC的转换汇率
        Map<String, BigDecimal> fromAssetToBTCPercentMap = Maps.newHashMap();
        // 暂存某个币种可以转换的可用余额
        Map<String, BigDecimal> transferMap = Maps.newHashMap();
        // btc转bnb的汇率
        BigDecimal btcToBNBPercent = getPrice(QUOTA_ASSET, BNB_ASSET, null);
        if (userAssets.size() > 0) {
            // EXCHANGE_DRIBBLET_PERCENTAGE 扣除的手续费
            if (StringUtils.isEmpty(EXCHANGE_DRIBBLET_PERCENTAGE)) {
                EXCHANGE_DRIBBLET_PERCENTAGE = this.sysConfigService.getValue(Constants.EXCHANGE_DRIBBLET_PERCENTAGE);
                if (StringUtils.isEmpty(EXCHANGE_DRIBBLET_PERCENTAGE)) {
                    EXCHANGE_DRIBBLET_PERCENTAGE = "2";
                }
            }
            // TRANSFER_BASE 小额资产标准，跟btc对比
            if (TRANSFER_BASE == null) {
                TRANSFER_BASE = new BigDecimal(this.sysConfigService.getValue(Constants.TRANSFER_BASE));
                if (TRANSFER_BASE == null) {
                    TRANSFER_BASE = new BigDecimal("0.001");
                }
            }
            // TRANSFER_UP_FLOAT 上浮百分比
            if (TRANSFER_UP_FLOAT == null) {
                TRANSFER_UP_FLOAT = new BigDecimal(this.sysConfigService.getValue(Constants.TRANSFER_UP_FLOAT));
                if (TRANSFER_UP_FLOAT == null) {
                    TRANSFER_UP_FLOAT = new BigDecimal("1.05");
                }
            }
            for (String asset : assetList) {
                boolean userHaveThisAsset = false;
                BigDecimal amount = BigDecimal.ZERO;
                // free是否足够
                for (UserAssetItem item : userAssets) {
                    if (item.getAsset().equals(asset)) {
                        // 过滤下架
                        if (filterIllegalAsset(item) == false) {
                            // result.put("success", false);
                            // result.put("msg", "asset.asset_not_enough");
                            // return result;
                            throw new BusinessException(GeneralCode.ASSET_ASSET_NOT_ENOUGH);
                        }
                        if (item.getFree().compareTo(amount) <= 0) {
                            // result.put("success", false);
                            // result.put("msg", "asset.asset_not_enough");
                            // return result;
                            throw new BusinessException(GeneralCode.ASSET_ASSET_NOT_ENOUGH);
                        }
                        amount = item.getFree();
                        userHaveThisAsset = true;
                        break;
                    }
                }
                if (!userHaveThisAsset) {
                    // result.put("success", false);
                    // result.put("msg", "asset.user_have_no_asset");
                    // return result;
                    throw new BusinessException(GeneralCode.ASSET_USER_HAVE_NO_ASSET);
                }
                // fromAsset-> BTC 汇率
                BigDecimal toBTCPercent = BigDecimal.ZERO;
                try {
                    toBTCPercent = getPrice(asset, QUOTA_ASSET, null);
                } catch (Exception e) {
                    log.error("获取汇率失败");
                }
                if (toBTCPercent == null) {
                    if (asset.equals(QUOTA_ASSET)) {
                        toBTCPercent = new BigDecimal("1");
                    } else {
                        toBTCPercent = new BigDecimal("0");
                    }
                }
                fromAssetToBTCPercentMap.put(asset, toBTCPercent);
                // Asset 转换为BTC：汇率乘可用余额
                BigDecimal toBTCPrice = toBTCPercent.multiply(amount);

                // 允许 0.001BTC 5% 的上浮动。否则报错。 0.001 ~0.00105
                if (TRANSFER_BASE.multiply(TRANSFER_UP_FLOAT).compareTo(toBTCPrice) < 0
                        || toBTCPrice.compareTo(BigDecimal.ZERO) <= 0) {
                    throw new BusinessException(GeneralCode.USER_OUT_OF_TRANSFER_FLOAT, new Object[] {asset});
                }
                // 如果转换成的BNB小于0.00000001,拒绝兑换。
                BigDecimal transferedBNBPrice = toBTCPercent.multiply(amount).multiply(btcToBNBPercent);
                if (btcToBNBPercent != null) {
                    BigDecimal value = transferedBNBPrice.subtract(transferedBNBPrice
                            .multiply(new BigDecimal(EXCHANGE_DRIBBLET_PERCENTAGE).divide(new BigDecimal("100"))));
                    if (value.compareTo(new BigDecimal("0.00000001")) < 0) {
                        log.info("用户兑换小额资产,小额资产扣除手续费后价值过低。" + asset + "：" + value);
                        throw new BusinessException(GeneralCode.USER_ASSET_AMOUNT_IS_TOO_LOW, new Object[] {asset});
                    }
                }
                transferMap.put(asset, amount);
            }
        } else {
            throw new BusinessException(GeneralCode.ASSET_USER_HAVE_NO_ASSET);
        }
        if (transferMap.isEmpty()) {
            throw new BusinessException(GeneralCode.ASSET_ASSET_NOT_ENOUGH);
        }
        if (ENABLE_REDIS_EXPIRES == 0) {
            try {
                ENABLE_REDIS_EXPIRES = this.sysConfigService.getInteger("enable_redis_expires");
            } catch (Exception e) {
                ENABLE_REDIS_EXPIRES = 1;
            }
        }
        // 是否启用24小时只能请求一次
        if (ENABLE_REDIS_EXPIRES == 1) {
            // 24内小时只能请求一次。
            IDistLock lock = this.distLockManager.buildRedDistLock(request.getUserId());
            try {
                if (!lock.tryLock(20, -1, TimeUnit.SECONDS)) {
                    throw new BusinessException(GeneralCode.GET_REDIS_LOCKED_FAILED);
                }
                if (RedisCacheUtils.get(request.getUserId(), String.class, DRIBBLET_REQUEST_REDIS_PREFIX) != null) {
                    throw new BusinessException(GeneralCode.USER_CAN_NOT_REQUEST_IN_24_HOURS);
                } else {
                    if (DRIBBLET_REQUEST_EXPIRES == 0) {
                        try {
                            DRIBBLET_REQUEST_EXPIRES =
                                    this.sysConfigService.getInteger(Constants.DRIBBLET_REQUEST_EXPIRES);
                        } catch (Throwable e) {
                            DRIBBLET_REQUEST_EXPIRES = 86400;
                        }
                    }
                    RedisCacheUtils.set(request.getUserId(), "", DRIBBLET_REQUEST_EXPIRES,
                            DRIBBLET_REQUEST_REDIS_PREFIX);
                }
            } finally {
                lock.unlock();
            }
        }
        DribbletAssetToBNBResponse transferResults = new DribbletAssetToBNBResponse();
        Long tranId = initTransactionCode(request.getUserId());
        // 手续费费率
        BigDecimal serviceChargePercent = new BigDecimal(EXCHANGE_DRIBBLET_PERCENTAGE).divide(new BigDecimal("100"));
        if (StringUtils.isEmpty(platformUserID)) {
            platformUserID = this.sysConfigService.getValue(Constants.EXCHANGE_AGNET);
            if (StringUtils.isEmpty(platformUserID)) {
                throw new BusinessException(GeneralCode.SYS_ERROR, "systemConfigError.platformUserID is not found");
            }
        }

        BigDecimal totalTransferedBNBPrice = BigDecimal.ZERO;
        BigDecimal totalTransferedBNBServicePrice = BigDecimal.ZERO;
        List<Long> assetDribbletIDList = Lists.newArrayList();

        Iterator<Entry<String, BigDecimal>> it = transferMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, BigDecimal> entry = it.next();
            String fromAsset = entry.getKey();
            BigDecimal amount = entry.getValue();
            // fromAsset-> BNB 汇率
            BigDecimal fromAssetToBNBPercent = BigDecimal.ZERO;
            try {
                fromAssetToBNBPercent = getPrice(fromAsset, BNB_ASSET, null);
            } catch (Exception e) {
                log.error("获取汇率失败");
            }
            BigDecimal transferedBNBPrice = null;
            BigDecimal servicePrice = null;
            // 没有转换成BNB的汇率
            if (fromAssetToBNBPercent == null) {
                BigDecimal transferedBTCPrice = amount.multiply(fromAssetToBTCPercentMap.get(fromAsset));
                transferedBNBPrice = btcToBNBPercent.multiply(transferedBTCPrice);
                servicePrice = transferedBNBPrice.multiply(serviceChargePercent);
                fromAssetToBNBPercent = fromAssetToBTCPercentMap.get(fromAsset).multiply(btcToBNBPercent);
            } else {
                transferedBNBPrice = amount.multiply(fromAssetToBNBPercent);
                servicePrice = transferedBNBPrice.multiply(serviceChargePercent);
            }
            if (transferedBNBPrice.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            // 小额资产转化日志。
            log.info("小额资产转化日志，流水号：" + tranId);
            Long assetDribbletLogId = addAssetDribbletLog(fromAsset, BNB_ASSET, tranId, amount,
                    userResponse.getUser().getEmail(), request.getUserId(), platformUserID, servicePrice,
                    fromAssetToBNBPercent, transferedBNBPrice);
            // 用户减fromAsset
            log.info("流水号：" + tranId + ",用户：" + request.getUserId() + "减去资产:" + fromAsset + ",数量：" + amount);
            AsssetBalanceRequest asssetBalanceRequest = new AsssetBalanceRequest();
            asssetBalanceRequest.setAmount(amount.negate());
            asssetBalanceRequest.setUserId(request.getUserId());
            asssetBalanceRequest.setAsset(fromAsset);
            asssetBalanceRequest.setTranId(tranId);
            asssetBalanceRequest.setType(Constants.DRIBBLET_EXCHANGE);
            asssetBalanceRequest.setCost(null);
            asssetBalanceRequest.setInfo("小额资产转换BNB");
            try {
                this.assetBalance(asssetBalanceRequest);
                // 平台加fromAsset,ipoable
                log.info("流水号：" + tranId + ",平台：" + platformUserID + "加上资产:" + fromAsset + ",数量：" + amount);
                UserAssetItem platformAsset;
                try {
                    platformAsset = userAssetRetryer
                            .call(() -> this.userAssetMapper.getUserAssetByUidAndAsset(platformUserID, fromAsset));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                platformAsset = setAgentAsset(platformAsset, platformUserID, fromAsset);
                platformAsset.setIpoable(amount);
                this.userAssetMapper.addCommission(platformAsset);
                this.userAssetLogService.addAssetLog(tranId, Constants.DRIBBLET_EXCHANGE, fromAsset, platformUserID,
                        amount, "小额资产平台加" + fromAsset, platformAsset);
                TransferResult tr = new TransferResult();
                tr.setFromAsset(fromAsset);
                tr.setAmount(amount);
                tr.setToBNB(transferedBNBPrice);
                transferResults.getTransferResult().add(tr);

                totalTransferedBNBPrice = totalTransferedBNBPrice.add(transferedBNBPrice);
                totalTransferedBNBServicePrice = totalTransferedBNBServicePrice.add(servicePrice);
                assetDribbletIDList.add(assetDribbletLogId);
            } catch (Exception e) {
                log.info("用户：" + request.getUserId() + "减去资产:" + fromAsset + "失败", e);
                throw e;
            }
        }

        if (totalTransferedBNBPrice.compareTo(BigDecimal.ZERO) > 0) {
            log.info("流水号：" + tranId + ",用户：" + request.getUserId() + "加上扣除手续费之后的资产:" + BNB_ASSET + ",数量："
                    + totalTransferedBNBPrice.subtract(totalTransferedBNBServicePrice) + ",手续费一共："
                    + totalTransferedBNBServicePrice);
            AsssetBalanceRequest asssetBalanceRequest = new AsssetBalanceRequest();
            asssetBalanceRequest.setAmount(totalTransferedBNBPrice.subtract(totalTransferedBNBServicePrice));
            asssetBalanceRequest.setUserId(request.getUserId());
            asssetBalanceRequest.setAsset(BNB_ASSET);
            asssetBalanceRequest.setTranId(tranId);
            asssetBalanceRequest.setType(Constants.DRIBBLET_EXCHANGE);
            asssetBalanceRequest.setCost(null);
            asssetBalanceRequest.setInfo("小额资产转换BNB");
            try {
                this.assetBalance(asssetBalanceRequest);
                log.info("流水号：" + tranId + ",平台：" + platformUserID + "减去扣除手续费之后的资产:" + BNB_ASSET + ",数量："
                        + totalTransferedBNBServicePrice.subtract(totalTransferedBNBPrice) + ",手续费一共："
                        + totalTransferedBNBServicePrice);
                asssetBalanceRequest = new AsssetBalanceRequest();
                asssetBalanceRequest.setAmount(totalTransferedBNBServicePrice.subtract(totalTransferedBNBPrice));
                asssetBalanceRequest.setUserId(platformUserID);
                asssetBalanceRequest.setAsset(BNB_ASSET);
                asssetBalanceRequest.setTranId(tranId);
                asssetBalanceRequest.setType(Constants.DRIBBLET_EXCHANGE);
                asssetBalanceRequest.setCost(null);
                asssetBalanceRequest.setInfo("小额资产转换BNB");
                try {
                    this.assetBalance(asssetBalanceRequest);
                    log.info("流水号：" + tranId + ",资产转换成功.");
                    if (assetDribbletIDList.size() > 0) {
                        log.info("日志数量：" + assetDribbletIDList.size() + "");
                        for (Long id : assetDribbletIDList) {
                            comfirmAssetDribbletLog(id);
                            log.info("assetDribbletLog日志:" + id + ",更新status成功");
                        }
                    }
                } catch (Exception e) {
                    log.info("平台：" + platformUserID + "減去扣除手续费之后的资产:" + BNB_ASSET + "失败", e);
                    throw new BusinessException(GeneralCode.SYS_ERROR);
                }
            } catch (Exception e) {
                log.info("用户：" + request.getUserId() + "加上扣除手续费之后的资产:" + BNB_ASSET + "失败", e);
                throw e;
            }
        }
        return transferResults;
    }

    private boolean filterIllegalAsset(UserAssetItem userAsset) {
        // 可用余额不大于0的币种，返回false
        if (userAsset.getFree().compareTo(new BigDecimal("0")) <= 0) {
            return false;
        }
        // 如果是BNB,返回false
        if (userAsset.getAsset().equals(BNB_ASSET)) {
            return false;
        }
        // 如果不是BTC
        if (userAsset.getAsset().equals(QUOTA_ASSET) == false) {
            // 获取这个币种到BNB转换的最短路径
            List<String> path = productService.getShortestPath(userAsset.getAsset(), BNB_ASSET);
            // 如果无最短转换路径则返回false
            if (path == null || path.size() == 0) {
                return false;
            }
            for (String symbol : path) {
                ProductItem product = productService.get(symbol);
                if (product == null) {
                    return false;
                }
                if (null != product.getTest() && product.getTest() == 1) {
                    return false;
                }
                if (!"TRADING".equals(product.getStatus())) {
                    return false;
                }
            }
        }
        return true;
    }

    private UserAssetItem setAgentAsset(UserAssetItem agentAsset, String userId, String asset) {
        if (null == agentAsset) {
            agentAsset = new UserAssetItem();
            agentAsset.setUid(userId);
            agentAsset.setAsset(asset);
            agentAsset.setFree(ZERO);
            agentAsset.setFreeze(ZERO);
            agentAsset.setLocked(ZERO);
            agentAsset.setIpoable(ZERO);
            agentAsset.setIpoing(ZERO);
            agentAsset.setStorage(ZERO);
            agentAsset.setWithdrawing(ZERO);
        }
        return agentAsset;
    }

    private void comfirmAssetDribbletLog(Long id) {
        try {
            retryer.call(() -> this.userAssetDribbletItemMapper.confirmUserAssetDribblet(1L, id));
        } catch (ExecutionException e) {
            log.warn(" userAssetDribblet,id={}, 更新状态失败{}", id, e);
        } catch (RetryException e) {
            log.warn(" userAssetDribblet,id={}, 更新状态失败{}", id, e);
        }
    }

    private Long addAssetDribbletLog(String fromAsset, String toAsset, Long tranId, BigDecimal amount, String email,
            String userId, String platformUserId, BigDecimal serviceChargeAmount, BigDecimal conversionPercent,
            BigDecimal transferedAmount) {
        UserAssetDribbletItem userAssetDribbletItem = new UserAssetDribbletItem();
        userAssetDribbletItem.setUid(userId);
        userAssetDribbletItem.setFromAsset(fromAsset);
        userAssetDribbletItem.setToAsset(toAsset);
        userAssetDribbletItem.setTranId(tranId);
        userAssetDribbletItem.setOperateTime(new Date());
        userAssetDribbletItem.setAmount(amount);
        userAssetDribbletItem.setEmail(email);
        userAssetDribbletItem.setPlatformUserId(platformUserId);
        userAssetDribbletItem.setServiceChargeAmount(serviceChargeAmount);
        userAssetDribbletItem.setConversionPercent(conversionPercent);
        userAssetDribbletItem.setTransferedAmount(transferedAmount);
        userAssetDribbletItem.setInfo("");
        userAssetDribbletItem.setStatus(0);
        this.userAssetDribbletItemMapper.addUserAssetDribblet(userAssetDribbletItem);
        return userAssetDribbletItem.getId();
    }

    private BigDecimal getPrice(String from, String to, Date date) {
        return this.productService.getPrice(from, to, date);
    }

    private Long initTransactionCode(String userId) {
        GetTranRequest item = new GetTranRequest();
        item.setDescription(String.format("用户  %s 转换小额资产转换BNB", userId));
        item.setTranType(80);
        item.setTime(new Date());
        return this.tranService.insert(item);
    }


    @Override
    public UserAssetTransferDribbletBtcResponse userAssetTransferDribbletBtc(
            UserAssetTransferDribbletBtcRequest request) throws Exception {
        UserAssetTransferDribbletBtcResponse response = new UserAssetTransferDribbletBtcResponse();
        List<UserAssetItem> list =
                this.userAssetMapper.getUserAsset(null, null, Arrays.asList(request.getUserId()), null);
        BigDecimal totalTransferBtc = ZERO;
        BigDecimal totalTransferBNB = ZERO;
        if (TRANSFER_BASE == null) {
            TRANSFER_BASE = new BigDecimal(this.sysConfigService.getValue(Constants.TRANSFER_BASE));
            if (TRANSFER_BASE == null) {
                TRANSFER_BASE = new BigDecimal("0.001");
            }
        }
        if (StringUtils.isEmpty(EXCHANGE_DRIBBLET_PERCENTAGE)) {
            EXCHANGE_DRIBBLET_PERCENTAGE = this.sysConfigService.getValue(Constants.EXCHANGE_DRIBBLET_PERCENTAGE);
            if (StringUtils.isEmpty(EXCHANGE_DRIBBLET_PERCENTAGE)) {
                EXCHANGE_DRIBBLET_PERCENTAGE = "2";
            }
        }
        BigDecimal exchange = new BigDecimal(EXCHANGE_DRIBBLET_PERCENTAGE).divide(new BigDecimal("100"));
        for (int i = 0; i < list.size(); i++) {
            UserAssetItem userAsset = list.get(i);
            if (filterIllegalAsset(userAsset) == false) {
                continue;
            }
            BigDecimal toBTCprice = BigDecimal.ZERO;
            BigDecimal toBNBprice = BigDecimal.ZERO;
            try {
                toBTCprice = getPrice(userAsset.getAsset(), QUOTA_ASSET, null);
                toBNBprice = getPrice(userAsset.getAsset(), BNB_ASSET, null);
                if (toBTCprice == null) {
                    toBTCprice = BigDecimal.ZERO;
                }
                if (toBNBprice == null) {
                    toBNBprice = BigDecimal.ZERO;
                }
            } catch (Exception e) {
                toBTCprice = BigDecimal.ZERO;
                toBNBprice = BigDecimal.ZERO;
            }
            BigDecimal totalAsset = new BigDecimal(String.valueOf(userAsset.getFree()))
                    .add(new BigDecimal(String.valueOf(userAsset.getFreeze())))
                    .add(new BigDecimal(String.valueOf(userAsset.getLocked())))
                    .add(new BigDecimal(String.valueOf(userAsset.getWithdrawing())));
            BigDecimal transferBtc = ZERO;

            if (userAsset.getAsset().equals(QUOTA_ASSET)) {
                transferBtc = totalAsset;
                toBTCprice = BigDecimal.ONE;
            } else {
                transferBtc = toBTCprice.multiply(totalAsset);
            }
            // 判断用总额判断，转化用free
            if (transferBtc.compareTo(TRANSFER_BASE) <= 0 && transferBtc.compareTo(BigDecimal.valueOf(0)) > 0) {
                BigDecimal freeToBtc = toBTCprice.multiply(userAsset.getFree());
                BigDecimal freeToBNB = toBNBprice.multiply(userAsset.getFree());

                BigDecimal exchangeFee = freeToBNB.multiply(exchange).setScale(8, BigDecimal.ROUND_HALF_UP);
                UserDribbletAsset uda = new UserDribbletAsset();
                uda.setAsset(userAsset.getAsset());
                uda.setAssetFullName(userAsset.getAssetName());
                uda.setAmountFree(userAsset.getFree().stripTrailingZeros().toPlainString());
                uda.setToBTC(freeToBtc.setScale(8, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString());
                uda.setToBNB(freeToBNB.setScale(8, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString());
                uda.setToBNBOffExchange(freeToBNB.subtract(exchangeFee).setScale(8, BigDecimal.ROUND_HALF_UP)
                        .stripTrailingZeros().toPlainString());
                uda.setExchange(exchangeFee.setScale(8, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString());
                response.getUserDribbletAssetList().add(uda);
                totalTransferBtc = totalTransferBtc.add(freeToBtc);
                totalTransferBNB = totalTransferBNB.add(freeToBNB);
            }
        }
        Collections.sort(response.getUserDribbletAssetList(), new Comparator<UserDribbletAsset>() {
            @Override
            public int compare(UserDribbletAsset a1, UserDribbletAsset a2) {
                return a1.getAsset().compareTo(a2.getAsset());
            }
        });
        response.setTotalTransferBNB(
                totalTransferBNB.setScale(8, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString());
        response.setTotalTransferBtc(
                totalTransferBtc.setScale(8, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString());
        response.setDribbletPercentage(exchange);
        return response;
    }

    @Override
    public UserAssetItem getOneUserAsset(GetOneUserAssetRequest request) throws Exception {
        return this.userAssetMapper.getUserAssetByUidAndAsset(request.getUserId(), request.getAsset());
    }

    // @Override
    // public void processTradeForBackfillTrades(ProcessTradeForBackfillTradesRequest request)
    // throws Exception {
    // String symbol = request.getSymbol();
    // Long tradeId = request.getTradeId();
    // JSONObject tradeObj = JSON.parseObject(request.getTradeObject());
    // // buy side
    // final long buyerOrderId = tradeObj.getLong("buyerOrderId");
    // final long buyerAcctId = tradeObj.getLong("buyerAcctId");
    // final String buyCommissionAsset = tradeObj.getString("buyCommissionAsset");
    // final String sellCommissionAsset = tradeObj.getString("sellCommissionAsset");
    // Boolean activeBuy = null;
    // if (tradeObj.containsKey("isBuyerMaker")) {
    // activeBuy = !tradeObj.getBoolean("isBuyerMaker");
    // }
    // this.processTrade(symbol, tradeId, buyerAcctId, new BigDecimal(tradeObj.getString("price")),
    // new BigDecimal(tradeObj.getString("qty")), tradeObj.getLong("time"), buyerOrderId, "BUY",
    // new BigDecimal(tradeObj.getString("buyCommission")), buyCommissionAsset, activeBuy, new
    // BigDecimal(0));
    //
    // // sell side
    // final long sellerOrderId = tradeObj.getLong("sellerOrderId");
    // final long sellerAcctId = tradeObj.getLong("sellerAcctId");
    // this.processTrade(symbol, tradeId, sellerAcctId, new BigDecimal(tradeObj.getString("price")),
    // new BigDecimal(tradeObj.getString("qty")), tradeObj.getLong("time"), sellerOrderId, "SELL",
    // new BigDecimal(tradeObj.getString("sellCommission")), sellCommissionAsset, activeBuy,
    // new BigDecimal(0));
    // }
    //
    // private void processTrade(final String symbol, final Long tradeId, final Long accountId,
    // final BigDecimal price,
    // final BigDecimal qty, final Long time, final Long orderId, final String side, final
    // BigDecimal commission,
    // final String commissionAsset, Boolean activeBuy, final BigDecimal totalAsset) throws
    // Exception {
    // GetUserIdByTradingAccountRequest getUserIdByTradingAccountRequest = new
    // GetUserIdByTradingAccountRequest();
    // getUserIdByTradingAccountRequest.setTradingAccount(accountId);
    // Long userId = this.getAPIRequestResponse(
    // this.userApi.getUserIdByTradingAccount(this.newAPIRequest(getUserIdByTradingAccountRequest)));
    // if (null == userId) {
    // log.warn("Can't find user for accountId: {}", accountId);
    // return;
    // }
    // TradeItem trade = new TradeItem();
    // trade.setTradeId(tradeId);
    // trade.setSymbol(symbol);
    // trade.setPrice(price);
    // trade.setQuantity(qty);
    // Date tradeTime = new Date(time);
    // String description = String.format("symbol: %s, tradeId: %s, side: %s", symbol, tradeId,
    // side);
    // GetTranRequest body = new GetTranRequest();
    // body.setDescription(description);
    // body.setTime(tradeTime);
    // body.setType(GetTranRequest.TranType.MONEY_TRADING);
    // Long tranId = this.tranService.insert(body);
    // log.debug(String.format("Transaction: %s, type: %s, time: %s, desc: %s", tranId,
    // Constants.MONEY_TRADING,
    // tradeTime, description));
    // Calendar c = Calendar.getInstance();
    // c.setTime(tradeTime);
    // c.set(Calendar.MILLISECOND, 0);
    // trade.setTime(c.getTime());
    // trade.setActiveBuy(activeBuy);
    // if ("BUY".equalsIgnoreCase(side)) {
    // trade.setBuyCommissionAsset(commissionAsset);
    // trade.setBuyerFee(commission);
    // trade.setBuyerOrderId(orderId);
    // trade.setBuyerUserId(String.valueOf(userId));
    // System.out.println(trade);
    // System.out.println(time);
    // System.out.println(DateUtils.format(tradeTime, "yyyy-MM-dd HH:mm:ss"));
    // tradeItemMapper.insert(trade);
    // this.updateTradingLedger(tranId, trade.getTradeId(), trade.getSymbol(), qty, price,
    // price.multiply(qty),
    // tradeTime, accountId, "BUY", trade.getBuyerFee(), commissionAsset, totalAsset, orderId);
    // } else {
    // trade.setSellCommissionAsset(commissionAsset);
    // trade.setSellerFee(commission);
    // trade.setSellerOrderId(orderId);
    // trade.setSellerUserId(String.valueOf(userId));
    // tradeItemMapper.insert(trade);
    // this.updateTradingLedger(tranId, trade.getTradeId(), trade.getSymbol(), qty, price,
    // price.multiply(qty),
    // tradeTime, accountId, "SELL", trade.getSellerFee(), commissionAsset, totalAsset, orderId);
    // }
    // }
    //
    // private void updateTradingLedger(Long tranId, Long tradeId, String symbol, BigDecimal qty,
    // BigDecimal price,
    // BigDecimal amount, Date tradeTime, Long accountId, String direction, BigDecimal fee, String
    // commissionAsset,
    // BigDecimal totalAsset, Long orderId) throws Exception {
    // ProductItem productItem = this.productService.get(symbol);
    // GetUserIdByTradingAccountRequest getUserIdByTradingAccountRequest = new
    // GetUserIdByTradingAccountRequest();
    // getUserIdByTradingAccountRequest.setTradingAccount(accountId);
    // Long userId = this.getAPIRequestResponse(
    // this.userApi.getUserIdByTradingAccount(this.newAPIRequest(getUserIdByTradingAccountRequest)));
    // if (userId == null) {
    // log.warn("Can't find user for accountId: {}", accountId);
    // return;
    // }
    // TradingLedgerItem item = new TradingLedgerItem();
    // item.setTranId(tranId);
    // item.setTradeId(tradeId);
    // item.setSymbol(symbol);
    // item.setAsset(commissionAsset);
    // item.setUserId(userId + "");
    // item.setDirection(direction);
    // item.setCommission(fee);
    // item.setTime(tradeTime);
    // if (fee.compareTo(ZERO) > 0) {
    // this.calcAgentRewardNew(fee, item, userId, tradeId, amount);
    // }
    // if (tradingLedgerItemMapper.insert(item) > 0) {
    // log.debug(
    // String.format("Transaction: %s, tradeId: %s, time: %s", tranId, item.getTradeId(),
    // item.getTime()));
    // // 金额变化记录
    // // user himself
    // BigDecimal delta = ZERO;
    // BigDecimal assetDelta = ZERO;
    // String info = "";
    // String assetInfo = "";
    // if ("BUY".equalsIgnoreCase(direction)) {
    // delta = ZERO.subtract(amount);
    // assetDelta = qty;
    // info = "交易支出";
    // assetInfo = "交易买入";
    // } else {
    // delta = amount;
    // assetDelta = ZERO.subtract(qty);
    // info = "交易收入";
    // assetInfo = "交易卖出";
    // }
    // // 买入方
    // UserAssetItem userAsset = this.getUserAsset(productItem.getBaseAsset(), userId);
    // this.addAssetLog(tranId, String.valueOf(userId), assetDelta, assetInfo, userAsset,
    // tradeTime);
    // // 花费
    // UserAssetItem sellAsset = this.getUserAsset(productItem.getQuoteAsset(), userId);
    // this.addAssetLog(tranId, String.valueOf(userId), delta, info, sellAsset, tradeTime);
    // // 手续费Log
    // UserAssetItem userAssetFee = this.getUserAsset(commissionAsset, userId);
    // this.addAssetLog(tranId, String.valueOf(userId), ZERO.subtract(fee), "交易手续费", userAssetFee,
    // tradeTime);
    // // agent1
    // if (item.getAgentReward1() != null && item.getAgentReward1().doubleValue() > 0) {
    // UserAssetItem agentAsset;
    // try {
    // agentAsset = userAssetRetryer.call(
    // () -> this.userAssetMapper.getUserAssetByUidAndAsset(item.getAgent1(), item.getAsset()));
    // } catch (Exception e) {
    // throw new RuntimeException(e);
    // }
    // agentAsset = setAgentAsset(agentAsset, item.getAgent1(), item.getAsset());
    // this.addAssetLog(tranId, item.getAgent1(), item.getAgentReward1(), "返佣收入", agentAsset,
    // tradeTime);
    // agentAsset.setIpoable(item.getAgentReward1());
    // // userAssetDao.addCommission(agentAsset);
    // this.userAssetMapper.addCommission(agentAsset);
    // }
    // // super agent account
    // if (item.getSuperAgentReward() != null && item.getSuperAgentReward().doubleValue() > 0) {
    // UserAssetItem agentAsset;
    // try {
    // agentAsset = userAssetRetryer.call(() -> userAssetMapper
    // .getUserAssetByUidAndAsset(String.valueOf(exchanegAgent.getUserId()), item.getAsset()));
    // } catch (Exception e) {
    // throw new RuntimeException(e);
    // }
    // agentAsset = setAgentAsset(agentAsset, String.valueOf(exchanegAgent.getUserId()),
    // item.getAsset());
    // this.addAssetLog(tranId, superAgentId, item.getSuperAgentReward(), "返佣收入", agentAsset,
    // tradeTime);
    // agentAsset.setIpoable(item.getSuperAgentReward());
    // // userAssetDao.addCommission(agentAsset);
    // this.userAssetMapper.addCommission(agentAsset);
    // }
    // }
    // }
    //
    // // 按照经纪人表 user_agent 来计算佣金
    // private void calcAgentRewardNew(BigDecimal fee, TradingLedgerItem item, Long userId, Long
    // tradeId,
    // BigDecimal amount) throws Exception {
    // BigDecimal sum = ZERO;
    // // 扣除清算费用，注意只更新sum，不更新usedRatio，这样保证下面的经纪人的佣金计算不被影响，事先从手续费扣除清算费，而不用在每次佣金计算判断是否已经影响到清算费用扣除
    // // UserAgentItem agent1 = agents.get(4);
    // UserIdRequest request = new UserIdRequest();
    // request.setUserId(userId);
    // GetUserResponse response =
    // this.getAPIRequestResponse(this.userApi.getUserById(this.newAPIRequest(request)));
    // Long agentId = response.getUserInfo().getAgentId();
    // if (agentId != null && (!StringUtils.equals(defaultAgentId, String.valueOf(agentId)))) {
    // // UserItem cu = userService.fetchUser(agentId);
    // request.setUserId(agentId);
    // response = this.getAPIRequestResponse(this.userApi.getUserById(this.newAPIRequest(request)));
    // BigDecimal ratio1 = response.getUserInfo().getAgentRewardRatio();
    // if (ratio1.compareTo(ZERO) > 0) {
    // BigDecimal reward1 = fee.multiply(ratio1).setScale(ASSET_PRECISION,
    // BigDecimal.ROUND_HALF_UP);
    // if (sum.add(reward1).compareTo(fee) > 0) {
    // reward1 = fee.subtract(sum);
    // }
    // sum = sum.add(reward1);
    // if (reward1.compareTo(ZERO) > 0) {
    // item.setAgent1(String.valueOf(agentId));
    // item.setAgent1Account(response.getUserInfo().getTradingAccount());
    // item.setAgentReward1(reward1);
    // }
    // }
    // }
    // // calc super agent reward and exchange income
    // if (sum.compareTo(fee) < 0) {
    // BigDecimal remain = fee.subtract(sum);
    //
    // BigDecimal superAgentReward =
    // remain.multiply(exchangeAgentRatio).setScale(ASSET_PRECISION, BigDecimal.ROUND_HALF_UP);
    // item.setSuperAgentReward(superAgentReward);
    // item.setExchangeIncome(remain.subtract(superAgentReward));
    // }
    // }
    //
    // private void addAssetLog(Long tranId, String userId, BigDecimal assetDelta, String assetInfo,
    // UserAssetItem userAsset, Date tradeTime) {
    // if (assetDelta.setScale(8, RoundingMode.DOWN).doubleValue() != 0) {
    // this.userAssetLogService.addAssetLog(userAssetLogItem -> {
    // userAssetLogItem.setUid(userId);
    // userAssetLogItem.setAsset(userAsset.getAsset());
    // userAssetLogItem.setTranId(tranId);
    // userAssetLogItem.setType(Constants.ASSET_TRADING);
    // userAssetLogItem.setDelta(assetDelta.setScale(8, RoundingMode.DOWN));
    // userAssetLogItem.setFree(userAsset.getFree());
    // userAssetLogItem.setLocked(userAsset.getLocked());
    // userAssetLogItem.setFreeze(userAsset.getFreeze());
    // userAssetLogItem.setWithdrawing(userAsset.getWithdrawing());
    // userAssetLogItem.setInfo(assetInfo);
    // userAssetLogItem.setTime(tradeTime);
    // });
    // }
    // }
    //
    // private UserAssetItem getUserAsset(String asset, Long userId) throws Exception {
    // GetOneUserAssetRequest request = new GetOneUserAssetRequest();
    // request.setAsset(asset);
    // request.setUserId(String.valueOf(userId));
    // UserAssetItem userAsset = this.getOneUserAsset(request);
    // if (userAsset == null) {
    // log.info("用户持仓记录未找到,创建新纪录, symbol:{}, user:{}", new Object[] {asset, userId});
    // userAsset = new UserAssetItem();
    // userAsset.setUid(String.valueOf(userId));
    // userAsset.setAsset(asset);
    // userAsset.setFree(ZERO);
    // userAsset.setLocked(ZERO);
    // userAsset.setFreeze(ZERO);
    // userAsset.setWithdrawing(ZERO);
    // }
    // return userAsset;
    // }

    @Override
    public void afterSingletonsInstantiated() {
        // this.init();
    }

    @Override
    public void assetTransfer(AssetTransferRequest request) throws Exception {
        AsssetBalanceRequest fromRequest = CopyBeanUtils.copy(request, AsssetBalanceRequest.class);
        fromRequest.setUserId(request.getFromUserId());
        fromRequest.setAmount(fromRequest.getAmount().negate());
        this.assetBalance(fromRequest);
        AsssetBalanceRequest toRequest = CopyBeanUtils.copy(request, AsssetBalanceRequest.class);
        toRequest.setUserId(request.getToUserId());
        this.assetBalance(fromRequest);
    }

	@Override
	public Integer getUserNegativeAsset(UserNegativeAssetRequest request) {
		return this.userAssetMapper.selectNegativeUserAsset(String.valueOf(request.getUserId()));
	}
}
